/**
 * 
 */
package pjoppert.util.muf;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Properties;
import java.util.StringTokenizer;
/**
 * <p>
 * Constroi arquivos para diversas utilidades a apartir de arquivo de entrada de
 * dados.
 * </p>
 * @author pjoppert
 */
public class MakingUsefulFiles {
	private File fileIn;
	private File fileOut;
	private Properties config;
	private BufferedReader reader;
	private BufferedWriter bufferedWriter;
	private int totLineOut = 0;
	private int totLineIn = 0;
	private String mainDelim = null;
	private Hashtable file_in_fields;
	private ArrayList line_out;
	private ArrayList file_in_pattern;
	private ArrayList file_in_delim;
	private String file_in_coment;
	/**
	 * 
	 */
	public MakingUsefulFiles() throws IOException {
		super();
		System.out.print(" Initializing ...");
		config = new Properties();
		config.load(MakingUsefulFiles.class.getResourceAsStream("muf.config"));
		if (config.isEmpty()) {
			System.out.println("\nERROR: File muf.config not found !");
			System.exit(1);
		}
		if (config.getProperty("file-in") == null || config.getProperty("file-in").trim().equals("")) {
			System.out.println("\n Invalid file_in property !");
			System.exit(1);
		}
		this.fileIn = new File(config.getProperty("file-in"));
		if (config.getProperty("file-out") == null || config.getProperty("file-out").trim().equals(""))
			fileOut = new File(fileIn.getAbsolutePath() + ".out");
		else fileOut = new File(config.getProperty("file-out"));
		bufferedWriter = new BufferedWriter(new FileWriter(fileOut));
		System.out.println("\n File out: " + fileOut.getAbsolutePath());
		reader = new BufferedReader(new InputStreamReader(System.in));
		file_in_coment = config.getProperty("file-in-coment");
		file_in_coment = file_in_coment == null ? "" : file_in_coment;
      System.out.println(" done.");
		configure();
	}
	/**
	 * 
	 */
	private void configure() {
		System.out.print(" Configuring ...");
		String aux;
		// -----------------------------------
		// Configurando delimitadores de campo.
		// ---------------------------------
		file_in_delim = new ArrayList(1);
		for (int i = 0; (aux = config.getProperty("file-in-fields-delim-" + i)) != null; i++) {
			file_in_delim.add(aux.trim());
		}      
		if (file_in_delim.size() <= 0)
			System.out.println(" Configure the property to delimit fields: file-in-delim-0=X  (DEFAULT=\";\")");
		else mainDelim = (String) file_in_delim.get(0);
		mainDelim = mainDelim == null || mainDelim.trim().equals("") ? ";" : mainDelim;
		file_in_pattern = new ArrayList(1);
		StringTokenizer tokenizerFileInPattern = new StringTokenizer(config.getProperty("file-in-pattern"), String.valueOf(mainDelim));
		while (tokenizerFileInPattern.hasMoreTokens())
			file_in_pattern.add(tokenizerFileInPattern.nextElement());
		// ---------------------------------
		// Configurando campos de entrada
		// ----------------------------------
		String file_in_fields_str = config.getProperty("file-in-fields");
		StringTokenizer tokenizer_fif = new StringTokenizer(file_in_fields_str, mainDelim);
		file_in_fields = new Hashtable(2);
		String tokenFif;
		while (tokenizer_fif.hasMoreTokens()) {
			tokenFif = tokenizer_fif.nextToken();
			Field field = new Field(tokenFif);
			String fieldConf = config.getProperty("file-in-fields-conf-" + tokenFif);
			if (fieldConf == null) {
				System.out.println(" Using default property for: file-in-fields-conf-" + field.name + "=" + field.name + ";");
				fieldConf = field.name;
			}			
         StringTokenizer subFieldTok = new StringTokenizer(fieldConf, mainDelim);
         runSubfields(null,subFieldTok,null,1);			
		}
		// ---------------------------------
		// Lendo registros de saida
		// ----------------------------------
		line_out = new ArrayList();
		if (config.getProperty("line-out-1").equals("")) {
			System.out.println("ERROR: line-out-1 is empty");
			System.exit(1);
		}
		String lineout = "";
		for (int i = 1; lineout != null; i++) {
			lineout = config.getProperty("line-out-" + i);
			if (lineout != null && !lineout.trim().equals(""))
				line_out.add(lineout);
		}
      System.out.println(" done.");
	}
	/**
	 * 
	 */
	private void runSubfields(Field field, StringTokenizer subFieldTok,StringTokenizer subFieldCurrent, int delimNum) {
		/* Para cada field-conf verifica e carrega strutura com subcampos */
		if (delimNum > file_in_delim.size()-1)
         return;
      String subDelim = (String)file_in_delim.get(delimNum);      					
		if (subFieldCurrent ==null)
			subFieldCurrent = new StringTokenizer(subFieldTok.nextToken(), subDelim);		
		if (subFieldCurrent.hasMoreTokens()) {
			if (field == null){
				field = new Field(subFieldCurrent.nextToken(), subDelim);
            file_in_fields.put(field.name, field);
         }else{
            field.nextField =  new Field(subFieldCurrent.nextToken(), subDelim);
            field = field.nextField;
         }             
			runSubfields(field ,subFieldTok, subFieldCurrent, ++delimNum );
		}else {
         if (subFieldTok.hasMoreTokens())
            subFieldCurrent = new StringTokenizer(subFieldTok.nextToken(), subDelim);
      }
	}
	/**
	 * Faz a leitura do arquivo de entrada de dados e escreve o arquivo de
	 * saida.
	 * @throws IOException
	 */
	private void make() throws IOException {
		totLineIn = 0;
		System.out.print(" Making ...");
		BufferedReader bufferedReader = null;
		try {
			bufferedReader = new BufferedReader(new FileReader(fileIn));
			String lineReaded = null;
			String lastLine = null;
			Record record = null;
			lineReaded = bufferedReader.readLine();
			Field field = null;
			while (lineReaded != null) {
				if (lineReaded.trim().equals("") || lineReaded.trim().substring(0, 1).equals(file_in_coment)) {
					lineReaded = bufferedReader.readLine();
					continue;
				}
				record = fillRecord(lineReaded);
				totLineIn++;
				StringBuffer lineOutBuffer = null;
				for (int i = 0; i < line_out.size(); i++) {
					if (record == null)
						continue;
					lineOutBuffer = new StringBuffer((String) line_out.get(i));
					for (int j = 0; j < record.fields.size(); j++) {
						field = (Field) record.fields.get(j);
						lastLine = lineOutBuffer.toString();
						while (lineOutBuffer.indexOf("[" + field.name + "]") >= 0)
							lineOutBuffer.replace(lineOutBuffer.indexOf("[" + field.name + "]"), (lineOutBuffer.indexOf("[" + field.name
									+ "]") + ("[" + field.name + "]").length()), field.value);
						while (field.nextField != null && lastLine.indexOf("[" + field.nextField.name + "]") >= 0) {
							field = field.nextField;
							if (field == null)
								break;
							lineOutBuffer.append("\n" + lastLine);
							totLineOut++;
							while (lineOutBuffer.indexOf("[" + field.name + "]") >= 0)
								lineOutBuffer.replace(lineOutBuffer.indexOf("[" + field.name + "]"), (lineOutBuffer.indexOf("["
										+ field.name + "]") + ("[" + field.name + "]").length()), field.value);
						}
					}
					bufferedWriter.write(lineOutBuffer.toString() + "\n");
					bufferedWriter.flush();
					totLineOut++;
				}
				lineReaded = bufferedReader.readLine();
			}
			System.out.println(" done.");
			System.out.println(" Line in :" + totLineIn);
			System.out.println(" Line out:" + totLineOut);
			System.out.println("\n .GAME OVER.");
			System.out.println(" Thanks for use pjoppert software.");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			bufferedReader.close();
		}
	}
	/**
	 * Preenche um Record com os dados de entrada. Campos e sub campos. TODO
	 * Verificar erro ao carregar subcampos.
	 * @param line Linha/Registro lido com os dados de entrada.
	 * @return
	 */
	private Record fillRecord(String line) {
		if (line == null)
			return null;
		StringTokenizer lineTokenizer = new StringTokenizer(line, String.valueOf(mainDelim));
		Iterator fileInPattern = file_in_pattern.iterator();
		String strField = null;
		String value = null;
		Field fieldConfig = null;
		// Campo NOh da estrutura.
		Field fieldAux = null;
		Record record = null;
		if (lineTokenizer.hasMoreTokens()) {
			// Vai lendo a linha enquanto houver campos na linha e na
			// configuracao de entrada.
			record = new Record();
			while (fileInPattern.hasNext()) {
				// Pega o proximo campo da configuracao de entrada para
				// verificar o padrao
				strField = (String) fileInPattern.next();
				fieldConfig = (Field) file_in_fields.get(strField);
				if (fieldConfig == null) {
					System.out.println(" fieldConfig(" + strField + ") not found.");
					continue;
				}
				/* Se o campo possui subcampos entao ler os campos internos */
				if (fieldConfig.nextField != null) {
					// Varrendo subcampos do campo atual.
					StringTokenizer internalFieldsValues = new StringTokenizer("");
					// preenche com os dados do arquivo de entrada.
					if (lineTokenizer.hasMoreTokens())
						internalFieldsValues = new StringTokenizer(lineTokenizer.nextToken(), (String) file_in_delim.get(1));
					else {// Preenche com os dados default
						String defaultConfig = config.getProperty("file-in-fields-default-" + fieldConfig.name);
						if (defaultConfig == null || defaultConfig.trim().equals(""))
							continue;
						internalFieldsValues = new StringTokenizer(defaultConfig, (String) file_in_delim.get(1));
					}
					// Ponto chave para carregar o registro de forma correta e
					// eficiente.
					fieldAux = null;
					// Enaquanto houver campos internos carrega na
					// estrutura.
					Field fieldConfigAux = fieldConfig.nextField;
					String fieldName = null;
					while (fieldConfigAux != null && internalFieldsValues.hasMoreTokens()) {
						fieldName = fieldConfigAux.name.equals("..") ? fieldName : fieldConfigAux.name;
						if (fieldAux == null) {
							fieldAux = new Field(fieldName);
							fieldAux.value = internalFieldsValues.nextToken();
							record.fields.add(fieldAux);
						} else {
							value = internalFieldsValues.nextToken();
							fieldAux.nextField = new Field(fieldName);
							fieldAux = fieldAux.nextField;
							fieldAux.value = value;
						}
						fieldConfigAux = fieldConfigAux.nextField;
					}
				} else {
					// Preenche com os dados do arquivo de entrada.
					if (lineTokenizer.hasMoreTokens())
						value = lineTokenizer.nextToken();
					else {// Preenche com os dados default
						String defaultConfig = config.getProperty("file-in-fields-default-" + fieldConfig.name);
						if (defaultConfig == null || defaultConfig.trim().equals(""))
							continue;
						value = new StringTokenizer(defaultConfig, mainDelim).nextToken();
					}
					fieldAux = new Field(fieldConfig.name);
					fieldAux.value = value;
					record.fields.add(fieldAux);
				}
			}
		}
		return record;
	}
	/**
	 * @throws IOException
	 */
	public void execute() throws IOException {
		String cmd = "";
		while (true) {
			System.out.print("muf> ");
			cmd = reader.readLine();
			if (cmd.equals("make"))
				make();
			else if (cmd.equals("help"))
				help();
			else if (cmd.equals("quit"))
				break;
			else System.out.println("comando inexistente. \nhelp - para listar comandos");
		}
		bufferedWriter.write("-- Line in :" + totLineIn + "\n");
		bufferedWriter.write("-- Line out:" + totLineOut + "\n");
		bufferedWriter.write("-- Thanks for using pjoppert Software --\n");
		bufferedWriter.flush();
		bufferedWriter.close();
	}
	/**
	 * 
	 */
	private void help() {
		System.out.println(" Making Useful Files (MUF) v0.1 build by pjoppert.");
		System.out.println(" - Commands:");
		System.out.println("   make    - make file ");
		// System.out.println(" file-in - mudar aquivo de entrada.");
		System.out.println("   help    - print this");
		System.out.println("   quit    - to get out.");
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			MakingUsefulFiles usefulFiles = new MakingUsefulFiles();
			if (args.length > 0 && args[0].trim().equalsIgnoreCase("-prompt")) {
				usefulFiles.execute();
				System.out.println(".GAME OVER.");
			} else usefulFiles.make();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
